#pragma once
#ifndef __linux__
#error unix local datagram only support linux system
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include "com.h"

namespace KRPC {

class LOCALDGRAM : public COM
{
private:
	static socklen_t size(const struct sockaddr_un &addr)
	{
		return offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path);
	}
	void setRandomAddr(struct sockaddr_un &addr)
	{
		char softlink[40] = {0}, exePath[128] = {0};
		sprintf(softlink, "/proc/%d/exe", getpid());
		int count = readlink(softlink, exePath, sizeof(exePath));
		assert(count > 0 && count <= (int)(sizeof(exePath)));
		const char *name = strrchr(exePath, '/');
		name = (name == nullptr) ? exePath : (name + 1);
		assert(strlen(name) + 29 <= sizeof(addr.sun_path));
		//前11个字节填'/tmp/local/',中间填name,后面17个字节填'_6位整数pid_dgram4位整数sockfd',最后1字节填'\0'
		struct stat st;
		if (stat("/tmp/local", &st) == -1 || S_ISDIR(st.st_mode) == 0) {
			(void) mkdir("/tmp/local", 0777);	//析构函数中增加了删除逻辑，不需要依赖其他进程去清除
		}
		// tmp 目录重启后会清空 进程名+pid+协议类型+socketfd , 在同一系统同一时刻具有唯一性
		int n = sprintf(addr.sun_path, "/tmp/local/%s_%d_dgram%d", name, getpid(), sockfd);
		addr.sun_path[n] = '\0';
		if (access(addr.sun_path, F_OK) == 0)
			unlink(addr.sun_path);
		addr.sun_family = AF_LOCAL;
	}
public:
	LOCALDGRAM(const char *filename) : maxSize(getWmemMaxSize())
	{
		if ((sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0)) != -1) {
			if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (const char *)&maxSize, sizeof(int))){/*fix Coverity*/}
			if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&maxSize, sizeof(int))){/*fix Coverity*/}
		}
		setRandomAddr(srcaddr);
		(void) bind(sockfd, (struct sockaddr *)&srcaddr, size(srcaddr));
		setDstAddr(filename);

		buf = (char *)malloc(maxSize);
	}

	~LOCALDGRAM()
	{
		if (sockfd == -1)
			return;
		close(sockfd);
		if (buf != nullptr) {
			free(buf);
			buf = nullptr;
		}
		unlink(srcaddr.sun_path);
	}

	virtual bool recv(Packet &packet, int msec) override
	{
		if(buf == nullptr)
			return false;
		setRcvTimeout(msec);
		memset(buf, 0, maxSize);
		auto rcvsz = ::recv(sockfd, buf, maxSize, 0);
		const MsgHead *h = (const MsgHead *)buf;
		if (rcvsz >= (ssize_t)sizeof(MsgHead) && h->len == rcvsz) {
			packet.resize(h->len);
			packet.reset();
			packet.write(buf, h->len);
			return true;
		} else {
			return false;
		}
	}

	void setRcvTimeout(int msec)
	{
		struct timeval tv = {msec / 1000, 1000 * (msec % 1000)};
		if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))){/*fix Coverity*/}
	}

	virtual bool send(const Packet &packet) override
	{
		auto sndsz = (ssize_t)packet.size();
		return (sndsz > maxSize) ? false : \
			(::sendto(sockfd, packet.data(), sndsz, 0, (const struct sockaddr *)&dstaddr, size(dstaddr)) == sndsz);
	}

	void setDstAddr(const char *filename)
	{
		std::string path = "/tmp/local/";
		path += filename;
		memset(&dstaddr, 0, sizeof(dstaddr));
		dstaddr.sun_family = AF_LOCAL;
		strcpy(dstaddr.sun_path, path.c_str());
	}

private:
	const int maxSize;
	char *buf;
	int sockfd;
	struct sockaddr_un srcaddr;
	struct sockaddr_un dstaddr;
	
	static int getWmemMaxSize()
	{
		int fd = open("/proc/sys/net/core/wmem_max", O_RDONLY);
		if (fd > 0) {
			char buf[40] = {0};
			int n = read(fd, buf, sizeof(buf));
			close(fd);
			if (n > 0) {
				return atoi(buf);
			}
		} 
		return 112460;
	}
};

}
